use chrono::Local;
use clap::Parser;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path;
use std::rc::Rc;
use std::str::FromStr;

/// 这是一个文本类型拆分工具，支持txt,csv
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    /// [必须]指定需要拆分文件路径
    #[arg(short, long)]
    file: String,
    /// [可选]指定文件输出目录
    #[arg(short, long)]
    out: Option<String>,

    /// 文件拆分大小单位M
    #[arg(short, long, default_value_t = 50)]
    size: usize,

    /// 是否开启gzip压缩文件[default:false]
    #[arg(short, long, default_value_t = false)]
    gzip: bool,
}

/**
 * 文件   拆分大小   压缩到指定的路径
 */

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut args: Args = Args::parse();
    pares_config(&mut args)?;
    let start_time = Local::now();
    let max_size = args.size * 1024 * 1024;
    println!("开始执行时间: {}", start_time);
    println!(
        "文件路径:{}\n生成路径:{:?}\n拆分大小:{} M\n",
        args.file,
        args.out,
        (max_size as f64 / 1024.0 / 1024.0).ceil(),
    );

    // print!("{}", input_file.split("/").last().unwrap().split(".").);
    let mut file_name;

    let file_suffix: &str;

    // 找到最后一个点的位置
    if let Some(pos) = args.file.rfind('.') {
        // 截取文件名，去掉最后的扩展名
        file_name = &args.file[..pos];
        if let Some(pos) = args.file.rfind('\\') {
            file_name = &file_name[pos + 1..];
        }
        file_suffix = &args.file[pos + 1..];
    } else {
        return Err("无法识别文件名称".into());
    }
    let metadata = fs::metadata(args.file.as_str())?; // 获取文件元数据
    let file_size = metadata.len(); // 获取文件字节大小
    println!(
        "文件总大小: {}/M,按照：{}/M 拆分 , 生成文件数: {}",
        (file_size as f64 / 1024.0 / 1024.0).ceil(),
        (max_size as f64 / 1024.0 / 1024.0).ceil(),
        (file_size as f64 / max_size as f64).ceil()
    );
    let mut reader = BufReader::new(File::open(args.file.as_str()).unwrap());
    let header_buffer = header_buffer(args.file.as_str());
    let mut last_buffer = Vec::new();
    if file_size > max_size.try_into().unwrap() {
        let mut file_number = 1;
        loop {
            // 暂时不要超过10G
            // 开始读取最大字节数
            let mut body_buffer = vec![0; max_size];
            println!("第 {}/次 读取,字节数量:{}", file_number, body_buffer.len());
            let body_size = reader.read(&mut body_buffer)?;
            println!("实际读取:{}", body_size);
            if body_size == 0 {
                break;
            }
            let output_file_path;
            if args.gzip {
                output_file_path = Path::new(&args.out.clone().unwrap())
                    .join(format!("{}_{}.{}.gz", file_name, file_number, file_suffix));
            } else {
                output_file_path = Path::new(&args.out.clone().unwrap())
                    .join(format!("{}_{}.{}", file_name, file_number, file_suffix));
            }
            let output = File::create(output_file_path)?;

            let mut writer: Box<dyn Write> = if args.gzip {
                Box::new(GzEncoder::new(output, Compression::fast()))
            } else {
                Box::new(BufWriter::new(output))
            };
            // 当实际读取已经小于读取字节数 结束循环
            if body_size < max_size {
                //添加头部
                if file_number != 1 {
                    writer.write_all(&header_buffer)?;
                }
                if !last_buffer.is_empty() {
                    writer.write_all(&last_buffer)?;
                }
                writer.write_all(&body_buffer[..body_size - 1])?;
                writer.flush()?;
                break;
            }

            // 查找换行符的位置
            if let Some(newline_pos) = body_buffer.iter().rposition(|&b| b == b'\n') {
                // 处理读取到换行符之前的内容
                let (line_part, rest) = body_buffer.split_at(newline_pos + 1);

                //添加头部
                if file_number != 1 {
                    writer.write_all(&header_buffer)?;
                }

                if !last_buffer.is_empty() {
                    writer.write_all(&last_buffer)?;
                }

                // 避免有空行 删除
                if let Some(b'\n') = line_part.last() {
                    // 将输入文件的内容写入压缩流
                    writer.write_all(&line_part[..line_part.len() - 2])?;
                } else {
                    writer.write_all(&line_part)?;
                }
                writer.flush()?;
                last_buffer = rest.to_vec(); // 剩余部分留待下次读取
            }

            file_number += 1;
        }
    } else {
        if args.gzip {
            println!("小于本次拆分文件数直接压缩");
            // 创建文件名称
            let output_file_path = Path::new(&args.out.unwrap())
                .join(format!("{}_{}.{}.gz", file_name, 1, file_suffix));
            // 新建文件
            let output_file = File::create(&output_file_path)?;
            // 拿到文件的gz 写入流
            let mut encoder = GzEncoder::new(output_file, Compression::fast());
            // Write the header to the first file
            // 将输入文件的内容写入压缩流
            io::copy(&mut reader, &mut encoder)?;
        } else {
            println!("文件大小不足以拆分");
        }
    }

    let end_time = Local::now();
    println!("执行结束时间: {}", end_time);
    println!(
        "本次执行时间: {}/ms",
        (end_time - start_time).num_milliseconds()
    );
    Ok(())
}

fn header_buffer(filename: &str) -> Vec<u8> {
    let mut reader = BufReader::new(File::open(filename).unwrap());
    let mut header_buffer = vec![0; 0];
    reader.read_until(b'\n', &mut header_buffer).unwrap();
    header_buffer
}

// 创建文件夹
fn ensure_directory_exists(dir_path: &str) -> std::io::Result<()> {
    if !fs::metadata(dir_path).is_ok() {
        fs::create_dir(dir_path)?;
    }
    Ok(())
}

fn pares_config(args: &mut Args) -> Result<(), Box<dyn std::error::Error>> {
    if let Some(out) = &args.out {
        ensure_directory_exists(out.as_str())?;
        let string = String::from_str(out.as_str()).unwrap();
        let path = Path::new(&string);
        let absolute_path = fs::canonicalize(&path)?;
        args.out = Some(
            absolute_path
                .as_path()
                .to_str()
                .unwrap()
                .to_string()
                .replace("\\\\?\\", ""),
        );
    } else {
        let path = Path::new(&args.file);
        if let Some(parent) = path.parent() {
            let absolute_path = fs::canonicalize(&parent)?;
            let out_path = Rc::new(
                absolute_path
                    .as_path()
                    .to_str()
                    .unwrap()
                    .to_string()
                    .replace("\\\\?\\", ""),
            );
            args.out = Some(Rc::clone(&out_path).to_string());
            ensure_directory_exists(Rc::clone(&out_path).as_str())?;
        }
    }
    Ok(())
}